﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Drawing;

namespace FiveInRow.Entity
{
    public class ChessStatus : IChessStatus
    {
        ConcurrentDictionary<int, IPiece> blackPieces = new ConcurrentDictionary<int, IPiece>();
        ConcurrentDictionary<int, IPiece> whitePieces = new ConcurrentDictionary<int, IPiece>();

        ConcurrentDictionary<int, PieceLine> rowLinesBlack = new ConcurrentDictionary<int, PieceLine>();
        ConcurrentDictionary<int, PieceLine> colLinesBlack = new ConcurrentDictionary<int, PieceLine>();
        ConcurrentDictionary<int, PieceLine> diagLinesBlack = new ConcurrentDictionary<int, PieceLine>();
        ConcurrentDictionary<int, PieceLine> reverseDiagLinesBlack = new ConcurrentDictionary<int, PieceLine>();

        ConcurrentDictionary<int, PieceLine> rowLinesWhite = new ConcurrentDictionary<int, PieceLine>();
        ConcurrentDictionary<int, PieceLine> colLinesWhite = new ConcurrentDictionary<int, PieceLine>();
        ConcurrentDictionary<int, PieceLine> diagLinesWhite = new ConcurrentDictionary<int, PieceLine>();
        ConcurrentDictionary<int, PieceLine> reverseDiagLinesWhite = new ConcurrentDictionary<int, PieceLine>();

        Player toGo;

        public int Rows
        {
            get;private set;
        }

        public int Columns
        {
            get;private set;
        }

        public ChessStatus(int rows,int cols)
        {
            this.Rows = rows;
            this.Columns = cols;
            this.toGo = Player.Black;
        }

        public void AddBlackPiece(IPiece piece)
        {
            blackPieces.TryAdd(ComputeHash(piece), piece);

            AddToLines(piece, rowLinesBlack, colLinesBlack, diagLinesBlack,reverseDiagLinesBlack);

            toGo = Player.White;
        }

        public void AddWhitePiece(IPiece piece)
        {
            whitePieces.TryAdd(ComputeHash(piece), piece);

            AddToLines(piece, rowLinesWhite, colLinesWhite, diagLinesWhite,reverseDiagLinesWhite);

            toGo = Player.Black;
        }

        public IPiece[] GetBlackPieces()
        {
            return blackPieces.Select(pair=>pair.Value).ToArray();
        }

        public IPiece[] GetWhitePieces()
        {
            return whitePieces.Select(pair => pair.Value).ToArray();
        }

        public Player WhoToGo()
        {
            return toGo;
        }

        public bool BlackWin()
        {
            if(HasFiveInRow(rowLinesBlack) || HasFiveInRow(colLinesBlack) || HasFiveInRow(diagLinesBlack) || HasFiveInRow(reverseDiagLinesBlack))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public bool WhiteWin()
        {
            if (HasFiveInRow(rowLinesWhite) || HasFiveInRow(colLinesWhite) || HasFiveInRow(diagLinesWhite) || HasFiveInRow(reverseDiagLinesWhite))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public bool Dead()
        {
            return blackPieces.Count + whitePieces.Count == Rows * Columns;
        }

        public bool HasPiece(int row, int col)
        {
            var hash = ComputeHash(row, col);
            if(blackPieces.ContainsKey(hash) || whitePieces.ContainsKey(hash))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        private int ComputeHash(IPiece piece)
        {
            return ComputeHash(piece.Row, piece.Column);
        }

        private int ComputeHash(int row,int col)
        {
            return col * 1000 + row;
        }

        private void AddToLines(IPiece piece,
            ConcurrentDictionary<int, PieceLine> rowLines,
            ConcurrentDictionary<int, PieceLine> colLines,
            ConcurrentDictionary<int, PieceLine> diagLines,
            ConcurrentDictionary<int, PieceLine> reverseDiagLines)
        {
            if (!rowLines.ContainsKey(piece.Row))
            {
                rowLines[piece.Row] = new PieceLine(PieceLine.LineType.RowLine, Rows);
            }
            rowLines[piece.Row].AddPiece(piece);

            if (!colLines.ContainsKey(piece.Column))
            {
                colLines[piece.Column] = new PieceLine(PieceLine.LineType.ColumnLine, Rows);
            }
            colLines[piece.Column].AddPiece(piece);

            int diagIndex = piece.Column - piece.Row;
            if (!diagLines.ContainsKey(diagIndex))
            {
                diagLines[diagIndex] = new PieceLine(PieceLine.LineType.Diagonal, Rows);
            }
            diagLines[diagIndex].AddPiece(piece);

            int reverseDiagIndex = this.Rows - 1 - piece.Row - piece.Column;
            if (!reverseDiagLines.ContainsKey(reverseDiagIndex))
            {
                reverseDiagLines[reverseDiagIndex] = new PieceLine(PieceLine.LineType.ReverseDiagonal, Rows);
            }
            reverseDiagLines[reverseDiagIndex].AddPiece(piece);
        }

        private bool HasFiveInRow(ConcurrentDictionary<int, PieceLine> lines)
        {
            foreach(var linePair in lines)
            {
                if(linePair.Value.HasFiveInRow())
                {
                    return true;
                }
            }

            return false;
        }

        class PieceLine
        {
            SortedDictionary<int, IPiece> pieces=new SortedDictionary<int, IPiece>();
            LineType type;
            int gridSize;

            public enum LineType
            {
                RowLine,
                ColumnLine,
                Diagonal,
                ReverseDiagonal
            }

            public PieceLine(LineType type,int gridSize)
            {
                this.type = type;
                this.gridSize = gridSize;
            }

            public void AddPiece(IPiece piece)
            {
                if(type == LineType.RowLine)
                {
                    pieces.Add(piece.Column, piece);
                }
                else if (type==LineType.ColumnLine)
                {
                    pieces.Add(piece.Row, piece);
                }
                else if (type == LineType.Diagonal)
                {
                    pieces.Add(Math.Min(piece.Row, piece.Column), piece);
                }
                else if (type == LineType.ReverseDiagonal)
                {
                    pieces.Add(Math.Min(gridSize - 1 - piece.Row, piece.Column), piece);
                }
            }

            public bool HasFiveInRow()
            {
                var pieceList = pieces.ToList();
                if (pieceList.Count == 0) return false;

                var prevIndex = pieceList.First().Key;
                int count = 1;
                for (int i = 1; i < pieceList.Count; ++i)
                {
                    var curIndex = pieceList[i].Key;
                    if (curIndex - prevIndex == 1)
                    {
                        count++;
                    }
                    else
                    {
                        count = 1;
                    }
                    prevIndex = curIndex;

                    if (count == 5)
                    {
                        return true;
                    }
                }

                return false;
            }
        }
    }
}
